#include "..\include\imp_typedefine.h"
#include "..\include\imp_io_wrapper.h"
#include "..\include\imp_os_wrapper.h"
#include "..\include\imp_sock_wrapper.h"
#include "winsock2.h"
#include "windns.h"

#define SOCKET_MAX_NUMBER 10
//#define BANDWIDTH_LIMITED //simulate GPRS network

#define IMP_Trace 

typedef struct _t_ime_socket
{
	SOCKET m_socket[SOCKET_MAX_NUMBER];
	WSAEVENT m_handle;
	Impsock_FuncType *callback;
	ImpTaskID task;
	ImpTaskID task_dns;
	void *param;
}t_ime_socket;

typedef struct
{
	ImpsockHandler inst;
	Int8 url[200];
	Int8 *ipAddr;
	void *param;
}t_impsock_dns_req;

#define IMPSOCK_DNS_REQ 1

#define Integ2charIp4(ipA, ip)	\
	IMP_Sprintf((ip), "%d.%d.%d.%d", (ipA)&0xff, ((ipA)>>8)&0xff, ((ipA)>>16)&0xff, ((ipA)>>24)&0xff)

static void	impsock_dns_0(ImpsockHandler inst, const Int8 *url, Int8 *ipAddr, void *param)
{
	t_ime_socket *hd = (t_ime_socket*)inst;
	PDNS_RECORD RR, rr;
	DNS_STATUS status = DnsQuery_A((PCSTR)url, DNS_TYPE_A, DNS_QUERY_BYPASS_CACHE, NULL, &RR, 0);
	t_impsock_info sock_report;

	if(status) //error
	{
		sock_report.param1 = (void*)hd->param;
		sock_report.param2 = (void*)param;
		sock_report.info = IMPSock_dns;
		ipAddr[0] = 0;
		hd->callback(&sock_report);
		return;
	}
	rr = RR;
	while(rr->wType != DNS_TYPE_A)
		rr = rr->pNext;

	Integ2charIp4(rr->Data.A.IpAddress, ipAddr);

	DnsRecordListFree(RR, DnsFreeRecordList);

	sock_report.param1 = (void*)hd->param;
	sock_report.param2 = (void*)param;
	sock_report.info = IMPSock_dns;
	hd->callback(&sock_report);
}


static ImpBool proc_sock_dns(void *param)
{
	t_ime_socket *hd = (t_ime_socket *)param;
	ImpMessage msg;
	while(impos_message_recv(hd->task_dns, &msg))
	{
		if(msg.id == IMPSOCK_DNS_REQ)
		{
			t_impsock_dns_req *dns_req = (t_impsock_dns_req*)msg.pBuffer;
			impsock_dns_0(dns_req->inst, dns_req->url, dns_req->ipAddr, dns_req->param);
		}
		IMP_Free(msg.pBuffer);
	}

	return IMP_True;
}

static ImpBool proc_sock(void *param)
{
	t_ime_socket *hd = (t_ime_socket *)param;

	t_impsock_info sock_report;
	sock_report.param1 = (void*)hd->param;

	while(1){
		UInt32 ret;
		Int32 errCode;
		WSANETWORKEVENTS wsaevent;
		Int32 sock_event, i;

		ret = WaitForSingleObject(hd->m_handle, INFINITE);
#ifdef BANDWIDTH_LIMITED
		Sleep(50);
#endif
		IMP_Assert(ret == WAIT_OBJECT_0, "socket signal error: %d\n", ret);
		WSAResetEvent(hd->m_handle);

		for(i=0; i<SOCKET_MAX_NUMBER; i++){
			if(hd->m_socket[i] == 0)
				continue;
			if(i>=SOCKET_MAX_NUMBER)
				break;
			sock_report.param2 = (void*)hd->m_socket[i];
			errCode = WSAEnumNetworkEvents(hd->m_socket[i], hd->m_handle, &wsaevent);
			if(errCode != 0)
			{
				IMP_Assert(0, "WSAEnumNetworkEvents error: %d-%d\n", errCode, WSAGetLastError());
				sock_report.info = IMPSock_error;
				hd->callback(&sock_report);
				continue;
			}
			sock_event = wsaevent.lNetworkEvents;
			while(sock_event && (hd->m_socket[i] != 0))
			{
				if((sock_event & FD_CONNECT) && (hd->m_socket[i] != 0))
				{
					IMP_Trace("FD_CONNECT\n");
					sock_report.info = IMPSock_connected;
					sock_event &= ~FD_CONNECT;
					hd->callback(&sock_report);
				}
				if((sock_event & FD_CLOSE) && (hd->m_socket[i] != 0))
				{
					IMP_Trace("FD_CLOSE\n");
					sock_report.info = IMPSock_disconnect;
					if(!((sock_event & FD_READ) | (sock_event & FD_WRITE)))
					{
						sock_event &= ~FD_CLOSE;
						hd->callback(&sock_report);
					}
				}
				if((sock_event & FD_READ) && (hd->m_socket[i] != 0))
				{
					IMP_Trace("FD_READ\n");
					sock_report.info = IMPSock_received;
					sock_event &= ~FD_READ;
					hd->callback(&sock_report);
				}
				if((sock_event & FD_WRITE) && (hd->m_socket[i] != 0))
				{
					IMP_Trace("FD_WRITE\n");
					sock_report.info = IMPSock_sent;
					sock_event &= ~FD_WRITE;
					hd->callback(&sock_report);
				}
			}
		}
	}

	return IMP_True;
}

ImpsockHandler impsock_open(Impsock_FuncType *callback, e_impsock_type type, void *param)
{
	IMP_Trace("impsock_open\n");
	WSADATA              wsaData;
	IMP_Assert(type == IMPSock_Type_TCP, "Don't support not-TCP socket: %d\n", type);
	//t_imeNetHandle *inst = (t_imeNetHandle*)host;
	Int32 ret = WSAStartup(MAKEWORD(2,2), &wsaData);
	IMP_Assert(ret==0, "WSAStartup error\n");

	t_ime_socket *hd = (t_ime_socket *)IMP_Malloc( sizeof(t_ime_socket) );

	IMP_Memset(hd, 0, sizeof(t_ime_socket));
	hd->callback = callback;
	hd->m_handle = WSACreateEvent();
	hd->param = param;

	hd->task_dns = impos_task_open(proc_sock_dns, (void *)hd, 0, 0);
	hd->task = impos_task_open(proc_sock, (void *)hd, 0, 0);

	return (ImpsockHandler)hd;
}

void	impsock_close(ImpsockHandler inst)
{
	IMP_Trace("impsock_close\n");
	t_ime_socket *hd = (t_ime_socket*)inst;
	WSACloseEvent(hd->m_handle);
	impos_task_close(hd->task);
	impos_task_close(hd->task_dns);
	IMP_Free( hd );
//	WSACleanup();
}

void	impsock_connect(ImpsockHandler inst, Int8 *ipAddr, Int32 port)
{
	IMP_Trace("impsock_connect\n");
	t_ime_socket *hd = (t_ime_socket*)inst;

	static SOCKADDR_IN          peerAddr;
	peerAddr.sin_family = AF_INET;
	peerAddr.sin_port = htons(port);    
	peerAddr.sin_addr.s_addr = inet_addr((const Int8*)ipAddr);

	if(hd->m_socket[0] == 0)
	{
		hd->m_socket[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		IMP_Assert(hd->m_socket[0]!=INVALID_SOCKET, "socket error\n");

		WSAEventSelect(hd->m_socket[0], hd->m_handle, FD_CONNECT | FD_CLOSE | FD_READ | FD_WRITE);

		Int32 ret = connect(hd->m_socket[0], (SOCKADDR*)&peerAddr, sizeof(peerAddr));
		
	}else
		IMP_Assert(0, "impsock_connect m_socket[0] has been connected\n");
}

void	impsock_disconnect(ImpsockHandler inst)
{
	IMP_Trace("impsock_disconnect\n");
	t_ime_socket *hd = (t_ime_socket*)inst;

	if(hd->m_socket[0] != 0)
	{
		Int32 ret = shutdown(hd->m_socket[0], SD_BOTH);
		IMP_Assert(ret == 0, "impsock_disconnect shutdown error: %d\n", ret);

		ret = closesocket(hd->m_socket[0]);
		IMP_Assert(ret == 0, "impsock_disconnect closesocket error: %d\n", ret);

		WSAEventSelect(hd->m_socket[0], hd->m_handle, 0);
		hd->m_socket[0] = 0;
	}
}

Int32	impsock_send(ImpsockHandler inst, Int8 *buffer, Int32 maxSize)
{
	t_ime_socket *hd = (t_ime_socket*)inst;

	Int32 ret = send(hd->m_socket[0], (const Int8*)buffer, maxSize, 0);
	IMP_Assert(ret != SOCKET_ERROR, "impsock_send: %d, %d\n", WSAGetLastError());

	if(ret == SOCKET_ERROR)
		return 0;
	return ret;
}

Int32	impsock_receive(ImpsockHandler inst, Int8 *buffer, Int32 maxSize)
{
	t_ime_socket *hd = (t_ime_socket*)inst;
#ifdef BANDWIDTH_LIMITED
	maxSize = IMP_MIN(512, maxSize);
#endif
	Int32 ret = recv(hd->m_socket[0], buffer, maxSize, 0);
	if(ret == SOCKET_ERROR){
		return 0;
	}
	return ret;
}

ImpsockChannelId	impsock_channel(ImpsockHandler inst)
{
	static SOCKADDR_IN          peerAddr;
	t_ime_socket *hd = (t_ime_socket*)inst;

	Int32 i;

	for(i=1; i<SOCKET_MAX_NUMBER; i++)
		if(hd->m_socket[i] == 0)
			break;
	
	if(i >= SOCKET_MAX_NUMBER)
		return 0;

	hd->m_socket[i] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	IMP_Assert(hd->m_socket[i]!=INVALID_SOCKET, "socket error\n");

	WSAEventSelect(hd->m_socket[i], hd->m_handle, FD_CONNECT | FD_CLOSE | FD_READ | FD_WRITE);

	return (ImpsockChannelId)hd->m_socket[i];
}

void	impsock_connect_channel(ImpsockHandler inst, Int8 *ipAddr, Int32 port, ImpsockChannelId channel)
{
	IMP_Trace("impsock_connect_channel\n");
	static SOCKADDR_IN          peerAddr;
	t_ime_socket *hd = (t_ime_socket*)inst;

	peerAddr.sin_family = AF_INET;
	peerAddr.sin_port = htons(port);    
	peerAddr.sin_addr.s_addr = inet_addr((const Int8*)ipAddr);

	Int32 ret = connect((SOCKET)channel, (SOCKADDR*)&peerAddr, sizeof(peerAddr));
	
	IMP_Assert(ret == SOCKET_ERROR, "connect non-block socket return: %d\n", ret);
}

void	impsock_disconnect_channel(ImpsockHandler inst, ImpsockChannelId channel)
{
	IMP_Trace("impsock_disconnect_channel\n");
	t_ime_socket *hd = (t_ime_socket*)inst;

	Int32 i;
	for(i=1; i<SOCKET_MAX_NUMBER; i++)
		if(hd->m_socket[i] == (SOCKET)channel)
			break;

	if(i >= SOCKET_MAX_NUMBER)
		return;

	Int32 ret = shutdown(hd->m_socket[i], SD_BOTH);
	IMP_Assert(ret == 0, "impsock_disconnect shutdown error: %d\n", ret);

	ret = closesocket(hd->m_socket[i]);
	IMP_Assert(ret == 0, "impsock_disconnect closesocket error: %d\n", ret);

	WSAEventSelect(hd->m_socket[i], hd->m_handle, 0);
	hd->m_socket[i] = 0;
}

Int32	impsock_send_channel(ImpsockHandler inst, ImpsockChannelId channel, Int8 *buffer, Int32 maxSize)
{
	Int32 ret = send((SOCKET)channel, (const Int8*)buffer, maxSize, 0);
	IMP_Assert(ret != WSAEWOULDBLOCK, "impsock_send_channel: %d, %d\n", channel, WSAGetLastError());
	
	if(ret == WSAEWOULDBLOCK)
	{
		return 0;
    }

	return ret;
}

Int32	impsock_receive_channel(ImpsockHandler inst, ImpsockChannelId channel, Int8 *buffer, Int32 maxSize)
{
#ifdef BANDWIDTH_LIMITED
	maxSize = IMP_MIN(512, maxSize);
#endif
	Int32 ret = recv((SOCKET)channel, buffer, maxSize, 0);

	if(ret == SOCKET_ERROR)
		return 0;

	return ret;
}

// asynchronous naturally
void	impsock_dns(ImpsockHandler inst, const Int8 *url, Int8 *ipAddr, void *param)
{
	t_ime_socket *hd = (t_ime_socket*)inst;

	ImpMessage msg;
	t_impsock_dns_req *dns_req = (t_impsock_dns_req*)IMP_Malloc(sizeof(t_impsock_dns_req));

	dns_req->inst = inst;
	IMP_Strcpy(dns_req->url, url);
	dns_req->ipAddr = ipAddr;
	dns_req->param = param;

	msg.id = (e_impos_msgId)IMPSOCK_DNS_REQ;
	msg.nSize = sizeof(t_impsock_dns_req);
	msg.pBuffer = (void*)dns_req;

	impos_message_send(hd->task_dns, &msg);
}

void impsock_network_reset(void)
{
	IMP_Trace("impsock_network_reset\n");
}

